import { FairyEditor, FairyGUI, System } from 'csharp';
import CodeWriter from './CodeWriter';

const NAMESPACE = "fgui";
// key->ProgressBar, value->组件名称列表，控制器名称列表
const COM_DEFAULT_NAMES: Map<string, [string[], string[]]> = new Map();

COM_DEFAULT_NAMES.set("ProgressBar", [["title", "bar", "bar_v", "ani"], []]);
COM_DEFAULT_NAMES.set("Button", [["title", "icon"], ["button"]]);
COM_DEFAULT_NAMES.set("ComboBox", [["title", "icon"], ["button"]]);
COM_DEFAULT_NAMES.set("Label", [["title", "icon"], []]);
COM_DEFAULT_NAMES.set("Slider", [["title", "bar", "bar_v", "grip"], []]);
COM_DEFAULT_NAMES.set("Component", [[], []]);

function getUnUseName(memberInfo: FairyEditor.PublishHandler.MemberInfo, type: string) {
    return `${memberInfo?.res?.owner?.name}/${type}`;
}

/**
 * 是否是fgui默认名称
 * n0_v1212as -> n0
 * @param name 
 * @returns 
 */
function isDefaultVarName(name: string, id: string) {
    if (!name || !id) return true;

    const idPre = id.split("_")[0];
    return idPre === name;
}

/**
 * 是否是fgui默认类
 * 不是组件自带的成员，并且不是默认命名
 * @param file 
 */
function isDefaultClass(file: string) {
    const xmlString = System.IO.File.ReadAllText(file);
    if (!xmlString) {
        console.warn("未知的文件:" + file);
        return false;
    }

    const xml = new FairyGUI.Utils.XML(xmlString);

    const extensionName = xml.GetAttribute("extention") || "Component";
    if (!COM_DEFAULT_NAMES.has(extensionName)) {
        console.warn("未知的扩展:" + extensionName);
        return false;
    }

    const options = COM_DEFAULT_NAMES.get(extensionName);

    for (let i = 0; i < xml.elements.Count; i++) {
        const e0 = xml.elements.get_Item(i);

        switch (e0.name) {
            case "controller": {
                const name = e0.GetAttribute("name");
                if (!options[1].includes(name)) {
                    return false;
                }
            } break;
            case "transition": {
                return false;
            } break;
            case "displayList": {
                for (let k = 0; k < e0.elements.Count; k++) {
                    const item = e0.elements.get_Item(k);
                    const name = item.GetAttribute("name");
                    const id = item.GetAttribute("id");

                    // 不是组件自带的成员，并且不是默认命名
                    if (!options[0].includes(name) && !isDefaultVarName(name, id)) {
                        console.warn("customNmae:" + name, "id:" + id);
                        return false;
                    }
                }
            } break;
        }
    }

    return true;
}

interface TruncInfo{
    branch:string,
    className:string,
    url: string,
}
/**
 * 类名与多语言的映射
 * @param classes 
 */
function getBranchInfos(handler: FairyEditor.PublishHandler, classes: System.Collections.Generic.List$1<FairyEditor.PublishHandler.ClassInfo>){
    var infos = new Map<string, Map<string, TruncInfo>>();
    const classCnt = classes.Count;
    for (let i: number = 0; i < classCnt; i++) {
        const classInfo = classes.get_Item(i);
        const res = classInfo.res;
        const className = classInfo.className;
        const branch = res.branch || 'Trunk';
        const url = `ui://${handler.pkg.id+classInfo.resId}`;
        var info = infos.get(className) || new Map<string, TruncInfo>();
        info.set(branch, {branch, className, url});
        infos.set(className, info);
    }
    return infos;
}
/**
 * 是否有分支
 * @param className 
 */
function checkHaveBranch(branchInfo: Map<string, TruncInfo>){
    return branchInfo.size>1 || (branchInfo.size==1&&!branchInfo.has('Trunk'));
}

class UIManifestItem {
    public path: string;
    public uiClassName: string;
}


function updateManifestFile(dir: string, pkg: string, items: UIManifestItem[]) {
    let indent = "  ";
    let reg = new RegExp(`// ${pkg}\n([^/]+)`);
    let filePath = dir + "/UIManifest.ts";
    let content = System.IO.File.Exists(filePath)?System.IO.File.ReadAllText(filePath):null;
    if(!content) {
        content = `//automated file\n`;
        content += `export class UIManifest {\n`;
        content += `//end\n`;
        content += `}`;
    }
    let match = reg.exec(content);
    let insertIdx = content.lastIndexOf("/") - 1;
    let deleteLen = 0;
    if(match) {
        insertIdx = match.index;
        deleteLen = match[0].length;
    }
    let newContent = `// ${pkg}\n`;
    for(let item of items) {
        if(item.uiClassName.startsWith("UI") || item.uiClassName.startsWith("Skin")) {
            newContent += `${indent}public static ${item.uiClassName} = "${item.path}@${pkg}";\n`;
        }
    }
    content = content.slice(0, insertIdx) + newContent + content.slice(insertIdx + deleteLen);
    System.IO.File.WriteAllText(dir + "/UIManifest.ts", content);
}

function genCode(handler: FairyEditor.PublishHandler) {
    const settings = (<FairyEditor.GlobalPublishSettings>handler.project.GetSettings("Publish")).codeGeneration;
    const codePkgName = handler.ToFilename(handler.pkg.name); //convert chinese to pinyin, remove special chars etc.
    const exportCodePath = handler.exportCodePath + '/' + codePkgName;
    const isThree = handler.project.type == FairyEditor.ProjectType.ThreeJS;
    const unImportMap = new Map<string, string>();

    // const namespaceName = settings.packageName ? settings.packageName + '.' + codePkgName : codePkgName;    

    const classes = handler.CollectClasses(settings.ignoreNoname, settings.ignoreNoname, NAMESPACE);
    handler.SetupCodeFolder(exportCodePath, "ts"); //check if target folder exists, and delete old files

    const getMemberByName = settings.getMemberByName;
    const classSX = settings.classNamePrefix;

    //trunk
    var branchInfos = getBranchInfos(handler, classes);
    //UI manifest
    var uiManifest: UIManifestItem[] = [];
    //UI
    const classCnt = classes.Count;
    const writer = new CodeWriter({ blockFromNewLine: false, usingTabs: true });
    for (let i: number = 0; i < classCnt; i++) {
        const classInfo = classes.get_Item(i);
        const members = classInfo.members;
        const references = classInfo.references;
        const branchInfo = branchInfos.get(classInfo.className);
        const res = classInfo.res;
        const haveBranch = checkHaveBranch(branchInfo);
        //先为主干生成代码
        const branch = res.branch || 'Trunk'
        if(branchInfo.has('Trunk') && branch!='Trunk'){
            continue;
        }

        writer.reset();

        const refCount = references.Count;
        const memberCnt = members.Count;
        const exported = [];

        for (let j: number = 0; j < memberCnt; j++) {
            let memberInfo = members.get_Item(j);

            if (memberInfo.res && handler.pkg !== memberInfo.res.owner && memberInfo.res.path[1] !== "_") {
                const type = classSX + memberInfo.res.name;

                if (exported.includes(type)) continue;

                // 判断是否为不导出文件
                const isEmpty = isDefaultClass(memberInfo.res.file);

                // console.log("name:" + memberInfo.name, "isDefaultClass:" + isEmpty);
                // 标记为空
                if (isEmpty) {
                    unImportMap.set(getUnUseName(memberInfo, type), memberInfo.type);
                }
                else {
                    exported.push(type);
                    writer.writeln('import %s from "../%s/%s";', type, memberInfo.res.owner.name, type);
                }
            }
        }

        if (refCount > 0) {
            for (let j: number = 0; j < refCount; j++) {
                let ref = references.get_Item(j);
                writer.writeln('import %s from "./%s";', ref, ref);
            }

            writer.writeln();
        }

        // if (isThree) {
            writer.writeln('import * as fgui from "fairygui-cc";');
            if (refCount == 0)
                writer.writeln();
        // }


        writer.writeln('export default class %s extends %s', classInfo.className, classInfo.superClassName);
        writer.startBlock();
        writer.writeln();

        for (let j: number = 0; j < memberCnt; j++) {
            let memberInfo = members.get_Item(j);
            const isInside = !memberInfo.res || handler.pkg === memberInfo.res.owner || memberInfo.res.path[1] === "_";
            const type = isInside ? memberInfo.type : classSX + memberInfo.res.name;

            const baseType = unImportMap.get(getUnUseName(memberInfo, type));
            writer.writeln('public %s!:%s;', memberInfo.varName, baseType ?? type);
        }
        writer.writeln('public static URL:string = "ui://%s%s";', handler.pkg.id, classInfo.resId);
        if(haveBranch){
            var branchStr = '';
            for(let [key,val] of branchInfo){
                branchStr+= `["${key}", "${val.url}"], `;
            }
            branchStr = `[${branchStr}]`;
            writer.writeln('public static URLS:Map<string, string> =new Map(%s);', branchStr);
        }
        writer.writeln('public static PackageURI:string = "%s";', handler.pkg.name);
        writer.writeln();

        writer.writeln('public static createInstance():%s', classInfo.className);
        writer.startBlock();
        if(haveBranch){
            writer.writeln('const branch = fgui.UIPackage.branch == "Trunk" ? "" : fgui.UIPackage.branch+"/";');
            writer.writeln('return <%s>(%s.UIPackage.createObject("%s", branch+"%s"));', classInfo.className, NAMESPACE, handler.pkg.name, classInfo.resName);
        }else {
            writer.writeln('return <%s>(%s.UIPackage.createObject("%s", "%s"));', classInfo.className, NAMESPACE, handler.pkg.name, classInfo.resName);
        }
        writer.endBlock();
        writer.writeln();

        writer.writeln('protected onConstruct():void');
        writer.startBlock();
        for (let j: number = 0; j < memberCnt; j++) {
            let memberInfo = members.get_Item(j);
            if (memberInfo.group == 0) {
                const isInside = !memberInfo.res || handler.pkg === memberInfo.res.owner || memberInfo.res.path[1] === "_";
                const type = isInside
                    ? memberInfo.type
                    : classSX + memberInfo.res.name;

                const baseType = unImportMap.get(getUnUseName(memberInfo, type));
                if (getMemberByName)
                    writer.writeln('this.%s = <%s>(this.getChild("%s"));', memberInfo.varName, baseType ?? type, memberInfo.name);
                else
                    writer.writeln('this.%s = <%s>(this.getChildAt(%s));', memberInfo.varName, baseType ?? type, memberInfo.index);
            }
            else if (memberInfo.group == 1) {
                if (getMemberByName)
                    writer.writeln('this.%s = this.getController("%s");', memberInfo.varName, memberInfo.name);
                else
                    writer.writeln('this.%s = this.getControllerAt(%s);', memberInfo.varName, memberInfo.index);
            }
            else {
                if (getMemberByName)
                    writer.writeln('this.%s = this.getTransition("%s");', memberInfo.varName, memberInfo.name);
                else
                    writer.writeln('this.%s = this.getTransitionAt(%s);', memberInfo.varName, memberInfo.index);
            }
        }
        writer.endBlock();

        writer.endBlock(); //class

        writer.save(exportCodePath + '/' + classInfo.className + '.ts');
        if(classInfo.className.startsWith("UI")) {
            //属于需要代码的界面
            uiManifest.push({
                path: `${handler.pkg.id}${classInfo.resId}`,// `${codePkgName}/${classInfo.className}`,
                uiClassName: classInfo.resName
            });
        }
    }

    //manifest file
    updateManifestFile(handler.exportCodePath, handler.pkg.name,  uiManifest);
    // writer.reset();
    // writer.writeln(`import {UiMgr} from 'UiMgr';`);          //注意，需要修改cocos creator 里的import-map，把UiMgr的路径加进去
    // writer.writeln(`export default class UIManifest {`);
    // writer.startBlock();
    // for(let item of uiManifest) {
    //     writer.writeln(`public static ${item.uiClassName} = "${item.path}";`);
    // }
    // writer.endBlock();
    // writer.writeln("}")
    // writer.save(handler.exportCodePath + "/" + "UIManifest.ts");

    
    //Binder
    writer.reset();

    let binderName = codePkgName + 'Binder';

    writer.writeln('import { UIManifest } from "../UIManifest";');
    writer.writeln();

    var classNames = new Set<string>();//分支过滤重复的
    for (let i: number = 0; i < classCnt; i++) {
        let classInfo = classes.get_Item(i);
        if(classNames.has(classInfo.className)){
            continue;
        }
        classNames.add(classInfo.className);
        writer.writeln('import %s from "./%s";', classInfo.className, classInfo.className);
    }

    // writer.writeln('import { SET_EXTENSION } from "../../extension/FguiExt";');
    // import { SET_EXTENSION } from "../../extension/FguiExt";

    // if (isThree) {
        writer.writeln('import * as fgui from "fairygui-cc";');
        writer.writeln();
    // }

    writer.writeln();
    writer.writeln('export default class %s', binderName);
    writer.startBlock();

    writer.writeln('public static bindAll():void');
    writer.startBlock();
    var classNameList = new Set<string>();
    for (let i: number = 0; i < classCnt; i++) {
        let classInfo = classes.get_Item(i);
        var branchInfo = branchInfos.get(classInfo.className);
        var branch = classInfo.res.branch;
        if(checkHaveBranch(branchInfo)){
            if(classNameList.has(classInfo.className)){
                continue;
            }
            classNameList.add(classInfo.className);
            writer.writeln('fgui.UIObjectFactory.setExtension(%s.URLS, %s);', classInfo.className, classInfo.className);
        }else{
            writer.writeln('fgui.UIObjectFactory.setExtension(%s.URL, %s);', classInfo.className, classInfo.className);
        }
    }
    writer.endBlock(); //bindall

    writer.endBlock(); //class

    writer.writeln(`UIManifest.binderMap.set("${handler.pkg.name}", ${binderName});`);

    writer.save(exportCodePath + '/' + binderName + '.ts');
}

export { genCode };